#include <stdio.h>
#include <iostream>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include "charsetconvert.h"
#include "senwordpro.h"

int CSenWordPro::init(const char* szCfgFile)
{
	int fd;
	char * o_buf = NULL;
	char * s = NULL;
	int iRes = 0;
	struct stat st;
	CCharSetConvert cs;

    //initialize for regexp
    this->initUtf8Range();

	if((fd = open(szCfgFile, O_RDONLY, 0644)) > 0)
	{
		if(fstat(fd, &st) == 0 && st.st_size > 0 && (s = (char *)calloc(1, st.st_size + 1)))
		{
			unsigned int o_size = st.st_size*3 + 1;
			o_buf = (char*)malloc(o_size);

			if(read(fd, s, st.st_size) > 0
				&& (iRes=cs.CharsetConvert(s, st.st_size, o_buf, o_size /*, "UTF-8"*/ )) > 0)
			{
#ifndef USE_REGEXP
				this->insertSenWord2( (const char*)o_buf, (unsigned int)iRes );
#else
                this->insertSenWordRegexp( (const char*)o_buf, (unsigned int)iRes );
#endif
			}
			free(s);
			s = NULL;
			free(o_buf);
			o_buf = NULL;
		}
		close(fd);
	}
	else
	{
		printf( "init: open file fail.\n" );
		return -1;
	}

    //this->m_swt.dump();
    //this->m_swtEmbed.dump();
    return 0;
}

string& CSenWordPro::TrimLeft(string& str, const char* pszTrim)
{
	size_t iIndex = str.find_first_not_of(pszTrim);
	if (iIndex != string::npos)
	{
		str.erase(0, iIndex);
	}

	return str;
}

string& CSenWordPro::TrimRight(string& str, const char* pszTrim)
{
	size_t iIndex = str.find_last_not_of(pszTrim);
	if ((++iIndex) != str.length())
	{
		str.erase(iIndex, str.length() - iIndex);
	}

	return str;
}

int CSenWordPro::insertSenWord( const char * src, unsigned int iuLen )
{
	const char * szBegin = src;
	char * pos = NULL;
	const char * szDelim = "|";
	int iDelimLen = strlen(szDelim);
	string strSenWord;

	while( (pos=strstr(const_cast<char*>(szBegin), const_cast<char*>(szDelim)))!=NULL )
	{
		strSenWord = string( szBegin, pos-szBegin );

		this->TrimLeft( strSenWord );
		this->TrimRight( strSenWord );

		this->m_swt.insert( strSenWord.c_str() );

		szBegin = pos + iDelimLen;
	}

	if( (unsigned int)(szBegin-src) < iuLen )
	{
		strSenWord = string(szBegin);

		this->TrimLeft( strSenWord );
		this->TrimRight( strSenWord );

		this->m_swt.insert( strSenWord.c_str() );
	}
	return 0;
}

int CSenWordPro::insertSenWord2( const char * src, unsigned int iuLen )
{
	const char * szBegin = src;
	char * pos = NULL;
	const char * szDelim = "|";
	int iDelimLen = strlen(szDelim);
	string strSenWord;
    string strBefore;
    string strAfter;
    bool bIsEmbeded = false;

	while( (pos=strstr(const_cast<char*>(szBegin), const_cast<char*>(szDelim)))!=NULL )
	{
        bIsEmbeded = false;
		strSenWord = string( szBegin, pos-szBegin );

		this->TrimLeft( strSenWord );
		this->TrimRight( strSenWord );

        //Check embeded word--start
        strBefore = string( src, szBegin-src );
        strAfter = string( pos );
        if( this->queryIsEmbedWord(strBefore, strSenWord)==0 ||
            this->queryIsEmbedWord(strAfter, strSenWord)==0 )
        {
            bIsEmbeded = true;
        }
        //Check embeded word--end

        if( bIsEmbeded )
        {
		    this->m_swtEmbed.insert( strSenWord.c_str() );
        }
        else
        {
		    this->m_swt.insert( strSenWord.c_str() );
        }

		szBegin = pos + iDelimLen;
	}

	if( (unsigned int)(szBegin-src) < iuLen )
	{
        bIsEmbeded = false;
		strSenWord = string(szBegin);

		this->TrimLeft( strSenWord );
		this->TrimRight( strSenWord );

        //Check embeded word--start
        strBefore = string( src, szBegin-src );
        if( this->queryIsEmbedWord(strBefore, strSenWord)==0 )
        {
            bIsEmbeded = true;
        }
        //Check embeded word--end

        if( bIsEmbeded )
        {
		    this->m_swtEmbed.insert( strSenWord.c_str() );
        }
        else
        {
		    this->m_swt.insert( strSenWord.c_str() );
        }
	}

	return 0;
}

/*
 * return value: 1->has sensitive word
 *               -1->not special char and sensitive word
 * */
int CSenWordPro::querySenWord(const char * srcContent, char **szRetSenWord) const
{
    int iRet = -1;

    //0:senword, -1:not senword
	iRet = this->m_swtEmbed.querySenWord( srcContent, szRetSenWord );
    if( iRet!=0 )
    {
	    iRet = this->m_swt.querySenWord( srcContent, szRetSenWord );
    }

    if( iRet==0 )   //has senword
    {
        iRet = 1;
    }
    else            //not has senword
    {
        iRet = 0;
    }

    return iRet;
}

/*
 * return value: 2->has special char
 *               1->has sensitive word
 *               0->not special char and sensitive word
 * */
int CSenWordPro::queryNickName(const char * srcContent)
{
    static RE2 patternSpecial("([[:space:][:punct:]\"\\#\\$%&'\\(\\)\\*\\+\\,\\./;\\<\\=\\>\\?\\^_\\|\\}]+)");
    string strUser = srcContent;
    string strMatch;
    int iRet = 0;

    bool bRet = RE2::PartialMatch( strUser, patternSpecial, &strMatch );
    if( bRet )
    {
        iRet = 2;
    }
    else
    {
#ifdef USE_REGEXP
        iRet = this->querySenWordRegexp( srcContent );
#else
        char * szRetSenWord = NULL;
        iRet = this->querySenWord( srcContent, &szRetSenWord );
#endif
    }

    return iRet;
}

int CSenWordPro::querySenWordRegexp(const char * srcContent)
{
    return this->realSenWordProcess( const_cast<char*>(srcContent), true );
}

/*return replace sensitive word counts
 *  * return value: >0, has sensitive word replace
 *   *               <0, no sensitive word replace
 *    * */
int CSenWordPro::replaceSenWord(char * srcContent)
{
#ifdef USE_REGEXP
    return this->realSenWordProcess( srcContent );
#else
    //return this->m_swt.replaceSenWord( srcContent ); //不改变位置
    return this->m_swt.replaceSenWord2( srcContent );   //改变位置
#endif
}

//query and replace, default bIsQuery=false is replace process, else query is sensitive word
int CSenWordPro::realSenWordProcess(char * srcContent, bool bIsQuery)
{
#ifdef USE_REGEXP
    static RE2 pattern(this->m_strRegPattern, this->m_re2opt);
    static RE2 patternEmbed(this->m_strRegPatternEmbed, this->m_re2opt);

    string strUser = srcContent;

    if( bIsQuery )
    {
        int iMatch = false;
        string strMatch;

        iMatch = RE2::GlobalReplace( &strUser, patternEmbed, "*");
        if( !(iMatch>0) )
        {
            iMatch = RE2::GlobalReplace( &strUser, pattern, "*");
        }
        return iMatch>0? 1:0;
    }
    else
    {
        memset( srcContent, 0, strUser.length() );

        RE2::GlobalReplace( &strUser, pattern, "***" );
        RE2::GlobalReplace( &strUser, patternEmbed, "***" );
        memcpy( srcContent, strUser.c_str(), strUser.length() );
        srcContent[strUser.length()] = 0;

        return strUser.length();
    }
#endif

    return 0;
}

int CSenWordPro::GetStringWidth(const char* szSrc)
{
	char * o_buf = NULL;
	int iRes = 0;
	CCharSetConvert cs;
    unsigned int iSrcLen = 0;

    iSrcLen = strlen(szSrc);
	if( iSrcLen > 0)
	{
        unsigned int o_size = iSrcLen*4 + 1;
        o_buf = (char*)malloc(o_size);

        iRes=cs.CharsetConvert(szSrc, iSrcLen, o_buf, o_size , "GB2312" );

        free(o_buf);
        o_buf = NULL;
	}
	else
	{
		printf( "GetStringWidth: source string error.\n" );
		return -1;
	}

    return iRes;
}

int CSenWordPro::initUtf8Range()
{
    m_vCharDec.push_back( CharsetDetect(0x00, 0x7F, 1) );
    m_vCharDec.push_back( CharsetDetect(0xC0, 0xDF, 2) );
    m_vCharDec.push_back( CharsetDetect(0xE0, 0xEF, 3) );
    m_vCharDec.push_back( CharsetDetect(0xF0, 0xF7, 4) );
    m_vCharDec.push_back( CharsetDetect(0xF8, 0xFB, 5) );
    m_vCharDec.push_back( CharsetDetect(0xFC, 0xFD, 6) );

    m_re2opt.set_max_mem(32<<20);

    m_bRegexpSet = false;

    return 0;
}

unsigned int CSenWordPro::getBytes(unsigned char uByte)
{
    for( vector<CCharsetDetect>::iterator itr=m_vCharDec.begin(); itr!=m_vCharDec.end(); itr++ )
    {
        if( uByte>=itr->uLower && uByte<=itr->uHigher )
        {
            return itr->nBytes;
        }
    }
    return 0;
}

/*
 * Return value: -1->Error string
 *               -2->not utf8 string
 * */
int CSenWordPro::addRegexpPattern(string & strSenWord, bool bIsEmbed)
{
    if( strSenWord.length()<1 )
    {
        return -1;
    }

    unsigned int i = 0;
    const char * szBegin = strSenWord.c_str();
    string strTmpPattern;
    bool bIsChinese = false;

    while( i<strSenWord.length() )
    {
        unsigned int nBytes = this->getBytes((unsigned char)szBegin[i]);
        if( nBytes==0 )
        {
            return -2;
        }

        if( nBytes==1 )
        {
            //back-slach escape
            switch( szBegin[i] )
            {
            case '.':
                {
                    strTmpPattern.append( "\\." );
                }
                break;
            case '?':
                {
                    strTmpPattern.append( "\\?" );
                }
                break;
            case '(':
                {
                    strTmpPattern.append( "\\(" );
                }
                break;
            case '=':
                {
                    strTmpPattern.append( "\\=" );
                }
                break;
            case '+':
                {
                    strTmpPattern.append( "\\+" );
                }
                break;
            case ')':
                {
                    strTmpPattern.append( "\\)" );
                }
                break;
            case '\\':
                {
                    strTmpPattern.append( "\\\\" );
                }
                break;
            default:
                {
                    strTmpPattern.push_back( (unsigned char)szBegin[i] );
                }
                break;
            }
        }
        else //bBytes>1
        {
            string strTmpWord = string(szBegin+i, nBytes);
            if( bIsChinese )
            {
                strTmpPattern.append( "[\\x00-\\xFF]{0,2}" );
                strTmpPattern.append( strTmpWord );
            }
            else
            {
                strTmpPattern.append( strTmpWord );
                bIsChinese = true;
            }
        }

        //szBegin += nBytes;
        i += nBytes;
    }

    if( bIsEmbed )
    {
        if(  this->m_strRegPatternEmbed.length()>0 ) // check the first set regexpssion
        {
           this->m_strRegPatternEmbed.append("|");
        }

        this->m_strRegPatternEmbed.append( strTmpPattern );
    }
    else
    {
        if( this->m_bRegexpSet && this->m_strRegPattern.length()>0 ) // check the first set regexpssion
        {
           this->m_strRegPattern.append("|");
        }
        else
        {
            this->m_bRegexpSet = true;
        }

        this->m_strRegPattern.append( strTmpPattern );
    }

    return 0;
}

int CSenWordPro::getRegexp(string & strRegexp)
{
    if( this->m_strRegPattern.length()>0 )
    {
        strRegexp = this->m_strRegPattern;
        return 0;
    }

    return -1;
}

int CSenWordPro::getRegexpEmbed(string & strRegexp)
{
    if( this->m_strRegPatternEmbed.length()>0 )
    {
        strRegexp = this->m_strRegPatternEmbed;
        return 0;
    }

    return -1;
}

int CSenWordPro::insertSenWordRegexp( const char * src, unsigned int iuLen )
{
	const char * szBegin = src;
	char * pos = NULL;
	const char * szDelim = "|";
	int iDelimLen = strlen(szDelim);
	string strSenWord;
    string strBefore;
    string strAfter;
    bool bIsEmbeded = false;

	while( (pos=strstr(const_cast<char*>(szBegin), const_cast<char*>(szDelim)))!=NULL )
	{
        bIsEmbeded = false;
		strSenWord = string( szBegin, pos-szBegin );

		this->TrimLeft( strSenWord );
		this->TrimRight( strSenWord );

        //Check embeded word--start
        strBefore = string( src, szBegin-src );
        strAfter = string( pos );
        if( this->queryIsEmbedWord(strBefore, strSenWord)==0 ||
            this->queryIsEmbedWord(strAfter, strSenWord)==0 )
        {
            bIsEmbeded = true;
        }
        //Check embeded word--end

		this->addRegexpPattern( strSenWord, bIsEmbeded );

		szBegin = pos + iDelimLen;
	}

	if( (unsigned int)(szBegin-src) < iuLen )
	{
        bIsEmbeded = false;
		strSenWord = string(szBegin);

		this->TrimLeft( strSenWord );
		this->TrimRight( strSenWord );

        //Check embeded word--start
        strBefore = string( src, szBegin-src );
        if( this->queryIsEmbedWord(strBefore, strSenWord)==0 )
        {
            bIsEmbeded = true;
        }
        //Check embeded word--end

		this->addRegexpPattern( strSenWord, bIsEmbeded );
	}

	return 0;
}

int CSenWordPro::queryIsEmbedWord(string& src, string& strKey)
{
    if( strKey.length()<1 ) return -1;

    char * pos=strstr(const_cast<char*>(src.c_str()), const_cast<char*>(strKey.c_str()));
    if( pos!=NULL )
    {
        return 0;
    }

    return -2;
}

